from typing import Any

from pyNastran.bdf.bdf import BDF
import json
import chardet
from loguru import logger
from transitions.extensions.diagrams_mermaid import invalid

from service.utils.code import convert_file_encoding


def detect_encoding(file_path):
    with open(file_path, "rb") as file:
        raw_data = file.read()
        result = chardet.detect(raw_data)
        return result["encoding"]


def preprocess_bdf_file(input_file, encoding="ascii"):
    with open(input_file, "r", errors="ignore") as f:
        lines = f.readlines()

    # 处理每行内容
    processed_lines = []
    for line in lines:
        # 移除行尾的\r\n并替换为\n
        line = line.rstrip("\r\n") + "\n"
        # 确保每行都是72或80字符
        if len(line.rstrip("\n")) < 72:
            line = line.rstrip("\n").ljust(72) + "\n"
        processed_lines.append(line)

    # 写入处理后的文件
    with open(input_file, "w", newline="\n", encoding=encoding) as f:
        f.writelines(processed_lines)


class HyperMeshBdfTransformer:
    """
    基于hm-bdf语法解析component的相关信息
    1. get_comps_info: 对应hypermesh结构树中components的信息,包括component名称、component id和prop名称
    2. get_comp_eids_ref: 获取每个component包含的单元id
    3. 目前这个逻辑的问题在于需要指定具体的bdf文件名称
    """

    def __init__(self, bdf_path: str):
        self.bdf_path = bdf_path
        if not self.check_bdf_generated():
            raise "the bdf file is not generated from hypermesh"
        else:
            with open(self.bdf_path, "rb") as file:
                self.lines = file.readlines()

    # 判断是否由hypermesh导出的bdf
    def check_bdf_generated(self, label=b"Generated by HyperMesh"):
        with open(self.bdf_path, "rb") as file:
            for line in file:
                if label in line:
                    # 由hypermesh导出的bdf
                    return 1
            else:
                # 非hypermesh导出的bdf
                return 0

    # HMMOVE 获取hmmove的索引位置
    def get_move_index(self):
        # 兼容bdf文件存在乱码
        start_marker = b"$HMMOVE"
        end_marker = b"$$"
        move_index = []
        start_index = 0
        for i, line in enumerate(self.lines):
            if i >= start_index and line.startswith(start_marker):
                for j, line2 in enumerate(self.lines):
                    if j > i and (
                        line.startswith(end_marker) or line2.startswith(start_marker)
                    ):
                        start_index = j
                        move_index.append((i, j))
                        break
        return move_index

    # 获取完整的eid描述行
    @staticmethod
    def get_move_eid(line: str):
        result = []
        for index in range(8, len(line), 8):
            data = line[index : index + 8].strip()
            try:
                eid = int(data)
            except:
                # print("THRU")
                result.append("THRU")
            else:
                result.append(eid)
        return result

    # 将THRU转化为真实的eid
    @staticmethod
    def expand_array(arr: list, label="THRU"):
        result = []
        for index, i in enumerate(arr):
            if i == label:
                temp = list(range(arr[index - 1] + 1, arr[index + 1]))
                result.extend(temp)
            else:
                result.append(i)
        return result

    # 获取cid与eids的对应关系
    def get_comp_eids_ref(self):
        # key: comp value: eids
        result = {}
        indexes = self.get_move_index()
        for index in indexes:
            content = self.lines[index[0] : index[1]]
            eids = []
            for i, line in enumerate(content):
                if i:
                    line_eids = HyperMeshBdfTransformer.get_move_eid(line.strip())
                    eids.extend(line_eids)
                else:
                    cid = int(line[8:].strip())
            if cid in result:
                result[cid].extend(HyperMeshBdfTransformer.expand_array(eids))
                pass
            else:
                result[cid] = HyperMeshBdfTransformer.expand_array(eids)
        return result

    def get_comp_by_eid(self, eid):
        result = self.get_comp_eids_ref()
        for k, v in result.items():
            if eid in v:
                return k

    # 获取所有comp的id、name和prop信息
    def get_comps_info(self):
        comps_info = []
        for line in self.lines:
            if line.startswith(b"$HMNAME COMP"):
                data = line.decode().split('"')
                comps_info.append(
                    {"cname": data[1], "cid": int(data[2].strip()), "pname": data[3]}
                )
        return comps_info

    # 根据g70_name获取所有的cid和所有的eids
    def get_comps_by_gname(self, gname):
        cids = []
        eids = []
        for k, v in self.get_comp_eids_ref().items():
            for comp_info in self.get_comps_info():
                if (k == comp_info["cid"]) and (
                    comp_info["cname"].split("_")[0] == gname
                ):
                    cids.append(k)
                    eids.extend(v)
        return cids, eids


class BdfElementType:
    CQUAD4 = "CQUAD4"
    CQUAD8 = "CQUAD8"
    CTRIA3 = "CTRIA3"
    CTRIA6 = "CTRIA6"
    CHEXA = "CHEXA"
    CPENTA = "CPENTA"
    CSHEAR = "CSHEAR"
    RBE2 = "RBE2"
    RBE3 = "RBE3"
    CROD = "CROD"
    CBAR = "CBAR"


class BdfTransformer:
    nodes = None
    nodeIds = None
    elements = None
    # CQUAD4元素
    cquad4_elements = None
    type_enum = None
    materials = None
    properties = None
    loads = None
    spcs = None

    def __init__(
        self, path, validate=True, xref=True, punch=False, only_render_info=False
    ):
        # 🏝️ 初始化变量
        self.nodes = []
        self.nodeIds = []
        self.elements = []
        self.materials = []  # 材料卡片封装
        self.properties = []  # 属性卡片封装
        self.loads = []  # 载荷卡片封装
        self.spcs = []  # 边界条件卡片封装
        self.cquad4_elements = {}
        self.type_enum = BdfElementType()
        self.model = BDF()
        encoding = detect_encoding(path)
        logger.info("文件编码 {}", encoding)
        if encoding == "ascii":
            try:
                preprocess_bdf_file(path, "ascii")
            except Exception:
                logger.error("文件换行符预处理失败")
            try:
                self.model.read_bdf(
                    path, validate=validate, xref=xref, punch=punch, encoding="latin1"
                )
            except RuntimeError:
                self.model.read_bdf(
                    path,
                    validate=validate,
                    xref=xref,
                    punch=punch,
                    encoding="utf-8",
                )
        else:
            convert_file_encoding(path, path, encoding, "ascii")
            preprocess_bdf_file(path, "ascii")
            self.model.read_bdf(
                path, validate=validate, xref=xref, punch=punch, encoding="ascii"
            )
        self.get_nodes()
        self.get_elements()
        if not only_render_info:
            self.get_materials()
            self.get_card_info()
            self.get_properties()
            self.get_loads()
            self.get_spcs()

    # 卡片信息列表
    def get_card_info(self):
        return self.model.card_count

    # 材料内容
    def get_materials(self):
        materials = []
        origin = self.model.materials
        for index, key in enumerate(origin.keys()):
            imat = {
                "id": key,
                "property": {},
                "type": "",
                "name": "material" + str(index),
            }
            mat = origin[key]
            if hasattr(mat, "name"):
                imat["name"] = mat["name"]
            mat_type = mat.type
            imat["type"] = mat_type
            if mat_type == "MAT1":
                p1 = {
                    "name": "弹性模量",
                    "value": mat.e,
                }
                imat["property"]["e"] = p1

                p2 = {
                    "name": "剪切模量",
                    "value": mat.g,
                }
                imat["property"]["g"] = p2

                p3 = {
                    "name": "泊松比",
                    "value": mat.nu,
                }
                imat["property"]["nu"] = p3

                p4 = {
                    "name": "密度",
                    "value": mat.rho,
                }
                imat["property"]["rho"] = p4

                p5 = {
                    "name": "热膨胀系数",
                    "value": mat.a,
                }
                imat["property"]["a"] = p5

                p6 = {
                    "name": "参考温度",
                    "value": mat.tref,
                }
                imat["property"]["tref"] = p6

                p7 = {
                    "name": "阻尼系数",
                    "value": mat.ge,
                }
                imat["property"]["ge"] = p7

                p8 = {
                    "name": "拉伸强度",
                    "value": mat.St,
                }
                imat["property"]["st"] = p8

                p9 = {
                    "name": "压缩强度",
                    "value": mat.Sc,
                }
                imat["property"]["sc"] = p9

                p10 = {
                    "name": "剪切强度",
                    "value": mat.Ss,
                }
                imat["property"]["ss"] = p10
            materials.append(imat)
        self.materials = materials
        return self.materials

    # 属性内容
    def get_properties(self):
        # 获取所有属性ID
        property_ids = list(self.model.properties.keys())

        # 遍历每个属性ID
        for prop_id in property_ids:
            # 获取属性对象
            prop = self.model.properties[prop_id]

            # 获取属性类型
            prop_type = prop.type

            # 根据属性类型解析属性详细内容
            if prop_type == "PSHELL":
                # PSHELL属性
                i_prop = {
                    "pid": {
                        "name": "属性ID",
                        "value": prop.pid,
                    },
                    "mid1": {
                        "name": "材料ID (MID1)",
                        "value": prop.mid1,
                    },
                    "t": {
                        "name": "厚度",
                        "value": prop.t,
                    },
                    "mid2": {
                        "name": "材料ID (MID2)",
                        "value": prop.mid2,
                    },
                    "twelveIt3": {
                        "name": "弯曲惯性矩 (12I/T^3)",
                        "value": prop.twelveIt3,
                    },
                    "mid3": {
                        "name": "材料ID (MID3)",
                        "value": prop.mid3,
                    },
                    "tst": {
                        "name": "横向剪切厚度",
                        "value": prop.tst,
                    },
                    "nsm": {
                        "name": "非结构质量",
                        "value": prop.nsm,
                    },
                    "z1": {
                        "name": "纤维距离 (Z1)",
                        "value": prop.z1,
                    },
                    "z2": {
                        "name": "纤维距离 (Z2)",
                        "value": prop.z2,
                    },
                    "mid4": {
                        "name": "材料ID (MID4)",
                        "value": prop.mid4,
                    },
                }
                self.properties.append(i_prop)

            elif prop_type == "PSOLID":
                # TODO
                # PSOLID属性
                pid = prop.pid
                mid = prop.mid
                cordm = prop.cordm
                in_ = prop.in_
                stress = prop.stress
                isop = prop.isop
                fctn = prop.fctn

                print(f"Property ID: {pid}")
                print(f"Property Type: {prop_type}")
                print(f"Material ID (MID): {mid}")
                print(f"Material Coordinate System (CORDM): {cordm}")
                print(f"Plane Strain or Axisymmetric (IN): {in_}")
                print(f"Output Stresses (STRESS): {stress}")
                print(f"Isotropic Material (ISOP): {isop}")
                print(f"Failure Theory (FCTN): {fctn}")
                print("------------------------")
        return self.model.properties

    # 载荷内容
    def get_loads(self):
        # 获取所有载荷ID
        load_ids = list(self.model.loads.keys())
        # 遍历每个载荷ID
        for load_id in load_ids:
            # 获取载荷对象
            loads = self.model.loads[load_id]
            for load in loads:
                # 获取载荷类型
                load_type = load.type

                # 根据载荷类型解析载荷详细信息
                # TODO 没做
                if load_type == "FORCE":
                    # FORCE载荷
                    sid = load.sid
                    node = load.node
                    cid = load.cid
                    mag = load.mag
                    xyz = load.xyz

                    print(f"Load ID: {load_id}")
                    print(f"Load Type: {load_type}")
                    print(f"SID: {sid}")
                    print(f"Node: {node}")
                    print(f"Coordinate System (CID): {cid}")
                    print(f"Magnitude: {mag}")
                    print(f"Force Direction (XYZ): {xyz}")
                    print("------------------------")

                elif load_type == "MOMENT":
                    # TODO 没做
                    # MOMENT载荷
                    sid = load.sid
                    node = load.node
                    cid = load.cid
                    mag = load.mag
                    xyz = load.xyz

                    print(f"Load ID: {load_id}")
                    print(f"Load Type: {load_type}")
                    print(f"SID: {sid}")
                    print(f"Node: {node}")
                    print(f"Coordinate System (CID): {cid}")
                    print(f"Magnitude: {mag}")
                    print(f"Moment Direction (XYZ): {xyz}")
                    print("------------------------")

                elif load_type == "PLOAD4":
                    i_load = {
                        "type": load_type,
                        "sid": {
                            "name": "载荷ID",
                            "value": load.sid,
                        },
                        "eids": {
                            "name": "单元IDs",
                            "value": load.sid,
                        },
                        "pressures": {
                            "name": "负载值",
                            "value": load.pressures.tolist(),
                        },
                        "g1": {
                            "name": "",
                            "value": load.g1,
                        },
                        "g34": {
                            "name": "",
                            "value": load.g34,
                        },
                        "cid": {
                            "name": "坐标系DI",
                            "value": load.cid,
                        },
                        "nvector": {
                            "name": "float vector",
                            "value": load.nvector.tolist(),
                        },
                        "surf_or_line": {
                            "name": "面或线",
                            "value": load.surf_or_line,
                        },
                        "line_load_dir": {
                            "name": "line load 方向",
                            "value": load.line_load_dir,
                        },
                        "comment": {
                            "name": "备注",
                            "value": load.comment,
                        },
                    }
                    self.loads.append(i_load)

    # 获取边界条件
    def get_spcs(self):
        # 获取所有SPC约束ID
        spc_ids = list(self.model.spcs.keys())
        # 遍历每个SPC约束ID
        for spc_id in spc_ids:
            # 获取SPC约束对象 ➡️ spcs 是个list
            spcs = self.model.spcs[spc_id]
            for spc in spcs:
                # 获取SPC约束类型
                spc_type = spc.type
                # 解析SPC约束详细信息
                if spc_type == "SPC":
                    # SPC约束
                    conid = spc.conid
                    nodes = spc.nodes
                    components = spc.components
                    enforced = spc.enforced
                    comment = spc.comment
                    i_spc = {
                        "type": "SPC",
                        "spc_id": {
                            "name": "约束卡片ID",
                            "value": spc_id,
                        },
                        "sid": {
                            "name": "约束ID",
                            "value": conid,
                        },
                        # Grid or scalar point identification number
                        "gi": {
                            "name": "节点IDs",
                            "value": nodes,
                        },
                        "ci": {
                            "name": "组件IDs",
                            "value": components,
                        },
                        "enforce": {
                            "name": "约束值",
                            "value": enforced,
                        },
                        "comment": {
                            "name": "备注",
                            "value": comment,
                        },
                    }
                    self.spcs.append(i_spc)

    # 获取nodes节点属性
    def get_nodes(self):
        if self.model.nodes:
            for nid, node in sorted(self.model.nodes.items()):
                position = node.xyz
                if node.cp == 0:
                    self.nodes.append(position[0])
                    self.nodes.append(position[1])
                    self.nodes.append(position[2])
                    self.nodeIds.append(node.nid)
                else:
                    # 将节点坐标转换到全局坐标系
                    coord = self.model.coords[node.cp]
                    global_xyz = coord.transform_node_to_global(node.xyz)
                    self.nodes.append(global_xyz[0])
                    self.nodes.append(global_xyz[1])
                    self.nodes.append(global_xyz[2])
                    self.nodeIds.append(node.nid)

    def get_elements(self):
        if self.model.elements:
            for index, element in sorted(self.model.elements.items()):
                meta = {
                    "type": element.type,
                    "nodeIds": [],
                    "eid": element.eid,
                    "faceIds": [],
                    "faceNums": [],
                }

                if element.type == self.type_enum.CQUAD4:
                    meta["nodeIds"] = element.node_ids
                    meta["faceIds"] = element.node_ids
                    meta["faceNums"] = [3]
                elif element.type == self.type_enum.CTRIA3:
                    meta["nodeIds"] = element.node_ids
                    meta["faceIds"] = element.node_ids
                    meta["faceNums"] = [2]
                elif element.type == self.type_enum.CSHEAR:
                    meta["nodeIds"] = element.node_ids
                    meta["faceIds"] = element.node_ids
                    meta["faceNums"] = [3]
                elif element.type == self.type_enum.CHEXA:
                    if len(element.node_ids) == 8:
                        meta["nodeIds"] = element.node_ids
                        meta["faceIds"] = [
                            element.node_ids[3],
                            element.node_ids[0],
                            element.node_ids[4],
                            element.node_ids[7],
                            element.node_ids[2],
                            element.node_ids[1],
                            element.node_ids[5],
                            element.node_ids[6],
                            element.node_ids[0],
                            element.node_ids[1],
                            element.node_ids[5],
                            element.node_ids[4],
                            element.node_ids[3],
                            element.node_ids[2],
                            element.node_ids[6],
                            element.node_ids[7],
                            element.node_ids[0],
                            element.node_ids[1],
                            element.node_ids[2],
                            element.node_ids[3],
                            element.node_ids[4],
                            element.node_ids[5],
                            element.node_ids[6],
                            element.node_ids[7],
                        ]
                        meta["faceNums"] = [3, 7, 11, 15, 19, 23]
                elif element.type == self.type_enum.CPENTA:
                    meta["nodeIds"] = element.node_ids
                    meta["faceIds"] = [
                        element.node_ids[0],
                        element.node_ids[1],
                        element.node_ids[4],
                        element.node_ids[3],
                        element.node_ids[1],
                        element.node_ids[4],
                        element.node_ids[5],
                        element.node_ids[2],
                        element.node_ids[2],
                        element.node_ids[5],
                        element.node_ids[3],
                        element.node_ids[0],
                        element.node_ids[0],
                        element.node_ids[1],
                        element.node_ids[2],
                        element.node_ids[3],
                        element.node_ids[4],
                        element.node_ids[5],
                    ]
                    meta["faceNums"] = [3, 7, 11, 14, 17]
                elif element.type == self.type_enum.RBE2:
                    print("RBE2")
                    # TODO 还未验证
                    # meta["masterNode"] = element.gn
                    # meta["slaveNode"] = element.gmi
                    # meta["specAttrs"] = dict()
                    # meta["specAttrs"]["freedom"] = element.c
                elif element.type == self.type_enum.RBE3:
                    # TODO 还未验证
                    print("RBE3")
                    # print("RBE3", element.g1, element.g2)
                elif element.type == self.type_enum.CROD:
                    meta["nodeIds"] = element.node_ids
                elif element.type == self.type_enum.CBAR:
                    meta["nodeIds"] = element.node_ids
                    if element.x is not None:
                        meta["specAttrs"] = {
                            # 方向 ndarray 要先转list
                            "x": element.x.tolist(),
                        }
                self.elements.append(meta)

    def to_json(self):
        data_set = {
            "cards": self.get_card_info(),
            "nodes": self.nodes,
            "elements": self.elements,
            "nodeIds": self.nodeIds,
            "materials": self.materials,
            "properties": self.properties,
            "loads": self.loads,
            "spcs": self.spcs,
        }
        json_str = json.dumps(data_set)
        return json_str


print("bdf扩展加载成功 版本v0.0.7")
